【Next.js】unauthorized.jsxとforbidden.jsx使ってみた

【Next.js】unauthorized.jsxとforbidden.jsx使ってみた

Clock Icon2024.12.12

リテールアプリ共創部のるおんです。
Next.jsのバージョン15から、認証・認可に関する新しい機能としてunauthorized.jsxforbidden.jsxが実験的に導入されました。これらの機能を使用することで、より簡単にアプリケーションの認証・認可の制御を行うことができます。

今回はこれらの機能について調べてみたので共有したいと思います。

https://nextjs.org/blog/next-15-1#forbidden-and-unauthorized-experimental

unauthorized.jsxとforbidden.jsxとは

これらは、Next.jsが提供する認証・認可のエラーハンドリングのための機能です。

Next.jsでは、notFound関数を呼び出すことでnot-found.tsxを表示させることができますが、これと同じように以下のファイルと関数がサポートされました。

  • unauthorized.tsx: unauthorized関数により呼び出される401エラー(未認証)時の表示コンポーネント
  • forbidden.tsx: forbidden関数により呼び出される403エラー(権限なし)時の表示コンポーネント

これらのファイルを用意することで、アプリケーション全体で統一された認証・認可のエラーハンドリングを実現することができます。

先にこれらの使い方を知りたい方は、使ってみるをまずご覧ください。

これらの関数は以下の場所で使用することができます:

  • Server Components
  • Server Actions
  • Route Handlers

いわゆるReactのサーバー側の処理をかける部分ですね。ただし、root layoutでは使用できません。

内部的には、これらの関数でErrorを発生させて、Errorのstatusコードに応じて表示させるコンポーネントを切り替えているようです。

  • 401エラー:unauthorized.tsx
  • 403エラー:forbidden.tsx
  • 404エラー:not-found.tsx

https://github.com/vercel/next.js/blob/363a7d4a527c56f795ff184c545fbcf38428e8ff/packages/next/src/client/components/http-access-fallback/http-access-fallback.ts#L48-L60

コンポーネントの階層は以下のようになるようです。

 <Layout>
   <Template>
     <ErrorBoundary fallback={<Error />}>
       <Suspense fallback={<Loading />}>
-       <ErrorBoundary fallback={<NotFound />}>
+        <ErrorBoundary fallback={<NotFound /> または  <Unauthorized /> または <Forbidden />}
           <Page />
         </ErrorBoundary>
       </Suspense>
     </ErrorBoundary>
   </Template>
 </Layout>

使ってみる

セットアップ

これらの機能を使用するには、2024/12/11時点でnext.config.jsで実験的機能を有効にする必要があります。

next.config.js
import type { NextConfig } from 'next'

const nextConfig: NextConfig = {
  experimental: {
+    authInterrupts: true,
  },
}

export default nextConfig

unauthorized.jsx

https://nextjs.org/docs/app/api-reference/functions/unauthorized

unauthorized関数を用いると、ログインしていないユーザーに対して自動でunauthorized.jsxを表示させることができます。

以下のように、unauthorized.jsxを作成しました。

src
└─ app
   ├─ page.jsx
+  ├─ unauthorized.jsx
   └─ lib
      └─ auth.js
unauthorized.jsx
export default function Unauthorized() {
  return (
    <div>
      <div>
        <h1>ログインしてください</h1>
        <form>
          <div>
            <label>メールアドレス</label>
            <input />
          </div>
          <div>
            <label>パスワード</label>
            <input />
          </div>
          <button>ログイン</button>
        </form>
      </div>
    </div>
  );
}

getServerSessionというユーザーのログイン情報を取得する関数があるとします。page.tsxにおいて、この関数を呼び出してログイン情報がない場合、unauthorized関数を呼び出すようにします。

page.jsx
import { getServerSession } from "./lib/auth";
import { unauthorized } from "next/navigation";
export default async function Page() {
  const session = await getServerSession();

+ if (!session) {
+   unauthorized();
+ }

  return (
    <div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]">
      <div>
        <h1>Hello World</h1>
      </div>
    </div>
  );
}

すると、以下の画像のように実際に未認証画面(unauthorized.tsx)を表示させることができます。

スクリーンショット 2024-12-12 10.02.12

forbidden.jsx

https://nextjs.org/docs/app/api-reference/functions/forbidden

次に、認証はできているが特定の権限(例:管理者権限)が必要なページでは、forbidden()関数を使用して権限無し画面(forbidden.jsx)を表示させることができます。
adminセグメントを作成し、forbidden.jsxを作成します。

src
└─ app
+  ├─ admin
+  │  ├─ forbidden.jsx
+  │  ├─ page.jsx
   └─ lib
      └─ auth.js
forbidden.jsx
export default function Forbidden() {
  return (
    <div className="flex h-screen items-center justify-center">
      <div className="rounded-lg bg-white p-8 shadow-lg">
        <h1 className="mb-4 text-2xl font-bold text-gray-800">Forbidden</h1>
        <p className="text-gray-600">権限がありません</p>
      </div>
    </div>
  );
}

例えば、page.jsxにおいてユーザーの権限がadmin権限じゃない場合、forbidden関数を呼び出します。

admin/page.jsx
import { forbidden, unauthorized } from "next/navigation";
import { getServerSession } from "../lib/auth";

export default async function Page() {
  const session = await getServerSession();

+ if (session.user.role == 'admin') {
+   forbidden();
+ }

  return (
    <main>
      <h1>Welcome to the Admin Page</h1>
      <p>Hi, {session.user.name}.</p>
    </main>
  );
}

forbidden関数が実行されると、以下のようにforbidden.jsxで定義したカスタムページが表示されます

スクリーンショット 2024-12-11 22.23.54

これまでと何が変わったか

これまでは、これらのユーザーの存在有無とロールなどの特定の属性に応じたリダイレクト先を自前で実装してあげる必要がありました。

これまで

これまでは、ログインしていない場合の画面や、権限がない場合の画面を専用で表示させるためには、それぞれディレクトを作成してセグメントを用意し、直接リダイレクトさせる必要がありました。

今回のアップデートで、リダイレクトさせずともURLのパスを変更しないまま描画するコンポーネントを変えられるようになったということです。

admin/page.tsx
import { forbidden, redirect, unauthorized } from "next/navigation";
import { getServerSession } from "../lib/auth";

export default async function Page() {
  const session = await getServerSession();

  if (!session) {
-   redirect("/login"); // これまでは、https://url/loginにリダイレクト
+   unauthorized(); 
  }

  if (session.user.role !== "admin") {
-   redirect("/forbidden"); // これまでは。https://url/forbiddenにリダイレクト
+   forbidden();
  }

  return (
    <main>
      <h1>Welcome to the Admin Dashboard</h1>
      <p>Hi, {session.user.role}.</p>
    </main>
  );
}

src
└─ app
   ├─ admin
+  │  ├─ forbidden.tsx
   │  └─ page.tsx
-  ├─ forbidden    // 各ルートを作成してあげる必要があった
-  │  └─ page.tsx
-  ├─ login
-  │  └─ page.tsx
   ├─ lib
   │  └─ auth.ts
   ├─ layout.tsx
   ├─ page.tsx
+  └─ unauthorized.tsx

これまでと変わらないところ

pageごとに認証チェックと権限チェックを行わないところは今までと変わりません。そのため、各セグメントのpage.tsxや各Server Actionなどにはこれまで通り毎回チェック処理と関数の呼び出しを書く必要があります。

毎回これらの関数を呼び出す書く必要があるのでそこまでコード量などに大きな変化は見られそうにないです。

おわりに

Next.jsのunauthorized.jsxforbidden.jsxを使用することで、アプリケーションの認証・認可の制御がより簡単になりました。

ただし、現時点では実験的な機能であるため、今後の正式リリースが待ちたいと思います。
以上。どなたかの参考になれば幸いです。

参考
https://nextjs.org/docs/app/api-reference/functions/unauthorized
https://nextjs.org/docs/app/api-reference/functions/forbidden

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.